#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <syserr.h>
#include <device.h>
#include <utils.h>
#include <sys/epoll.h>
#include "dev/stream.h"
#include "printk.h"


struct speed_map
{
    u32 speed;
    int system;
};
static const struct speed_map speed_maps[] =
{
    {115200, B115200}, {38400, B38400}, {19200, B19200}, {9600, B9600},
    {4800, B4800}, {2400, B2400}, {1200, B1200}, {600, B600}, {300, B300},
};

static int get_system_speed(u32 speed)
{
    int i;

    for (i = 0; i < array_size(speed_maps); i++)
    {
        if (speed_maps[i].speed == speed)
            return speed_maps[i].system;
    }
    return -1;
}

static int setspeed(int fd, u32 speed)
{
    int system_speed = get_system_speed(speed);
    if (system_speed < 0)
        return system_speed;

    struct termios term;
    if (tcgetattr(fd, &term) != 0)
        return -1;

    cfsetispeed(&term, system_speed);
    cfsetospeed(&term, system_speed);

    return tcsetattr(fd, TCSAFLUSH, &term);
}

static int setattr(int fd, size_t databits, size_t stopbits, char parity)
{
    struct termios term;

    if (tcgetattr(fd, &term) != 0)
        return -1;

    term.c_cflag &= ~CSIZE;
    term.c_cflag |= CLOCAL | CREAD;
    term.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    term.c_oflag &= ~(OPOST | OLCUC | ONLRET | ONOCR | OCRNL);
    term.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | INLCR | IGNCR |
                      IUCLC | PARMRK);
    switch (databits)
    {
    case 5:
        term.c_cflag |= CS5;
        break;
    case 6:
        term.c_cflag |= CS6;
        break;
    case 7:
        term.c_cflag |= CS7;
        break;
    case 8:
        term.c_cflag |= CS8;
        break;
    default:
        return -1;
    }

    switch (parity)
    {
    case 'n':
    case 'N':
        term.c_iflag &= ~INPCK;
        term.c_cflag &= ~PARENB;
        break;
    case 'o':
    case 'O':
        term.c_iflag |= INPCK;
        term.c_cflag |= PARENB;
        term.c_cflag |= PARODD;
        break;
    case 'e':
    case 'E':
        term.c_iflag |= INPCK;
        term.c_cflag |= PARENB;
        term.c_cflag &= ~PARODD;
        break;
    default:
        return -1;
    }

    switch (stopbits)
    {
    case 1:
        term.c_cflag &= ~CSTOPB;
        break;
    case 2:
        term.c_cflag |= CSTOPB;
        break;
    default:
        return -1;
    }

    term.c_cc[VTIME] = 0;
    term.c_cc[VMIN] = 0;

    return tcsetattr(fd, TCSAFLUSH, &term);
}

struct uart_port
{
    struct stream_device    dev;
    struct usart_cfg        cfg;
};
#define to_port(stream_dev) (container_of(stream_dev, struct uart_port, dev))

/*
 * 0  ~ 19 ttySx
 * 20 ~ 39 ttyUSBx
 * 40 ~ 59 ttyRSx
 */
#define UART_S_OFF      (0)
#define UART_USB_OFF    (20)
#define UART_RS_OFF     (40)

static int uart_do_cfg(int fd, const struct usart_cfg *cfg)
{
    if (setspeed(fd, cfg->baud) != 0)
        return -1;

    if (setattr(fd, cfg->databits, 1, cfg->parity) != 0)
        return -1;

    return 0;
}

static int parse_devname(const char *devname, char *realname, int maxlen)
{
    const char *format;
    int uartno;

    if (!startswith(devname, "uart"))
        return snprintf(realname, maxlen, "/dev/%s", devname);

    uartno = strtoul(devname + 4, NULL, 10);
    if (uartno >= UART_RS_OFF)
    {
        format = "/dev/ttyRS%d";
        uartno -= UART_RS_OFF;
    }
    else if (uartno >= UART_USB_OFF)
    {
        format = "/dev/ttyUSB%d";
        uartno -= UART_USB_OFF;
    }
    else
    {
        format = "/dev/ttyS%d";
    }
    return snprintf(realname, maxlen, format, uartno);
}

static int uart_open(const char *devname, const struct usart_cfg *cfg)
{
    int fd;
    char dev[32];

    parse_devname(devname, dev, sizeof(dev));
    if ((fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0)
    {
        DEV_PE("<%s> open call fault!\n", dev);
        return -ENXIO;
    }

    printf("<%s>: open as baud[%d], databits[%d], parity[%c]\n",
           dev, cfg->baud, cfg->databits, cfg->parity);

    if (uart_do_cfg(fd, cfg) < 0)
    {
        DEV_PE("<%s> set cfg fault!\n", dev);
        close(fd);
        return -EAGAIN;
    }

    return fd;
}

static int usart_open(struct stream_device *stream_dev)
{
    device_t *dev = to_dev(stream_dev);
    struct uart_port *port = to_port(stream_dev);

    int fd = uart_open(obj_name(&dev->obj), &port->cfg);
    if (fd > 0)
    {
        ev__io_init(&dev->io, device_io, fd);
        ev__io_start(dev_loop(dev), &dev->io, EPOLLIN);
        dev_status(dev) = DEVICE_STATUS_CONNECTED;
    }
    return fd;
}

static void usart_eof(struct stream_device *stream_dev)
{
    device_t *dev = to_dev(stream_dev);

    if (dev->usr_cb && dev->usr_cb->ev_cb)
        dev->usr_cb->ev_cb(dev, DE_DISCONN, NULL);
}

static int usart_ctrl(struct stream_device *stream_dev, u32 cmd, void *args)
{
    device_t *dev = to_dev(stream_dev);
    struct uart_port *port = to_port(stream_dev);

    switch (cmd)
    {
    case DEV_CTRL_CMD_GET_CFG:
        memcpy(args, &port->cfg, sizeof(struct usart_cfg));
        break;
    case DEV_CTRL_CMD_SET_CFG:
        if (!memcmp(&port->cfg, args, sizeof(struct usart_cfg)))
            return 0;

        if (ev__is_active(dev))
        {
            if (uart_do_cfg(dev_fd(dev), (struct usart_cfg *)args) < 0)
                return -EIO;
        }
        memcpy(&port->cfg, args, sizeof(struct usart_cfg));
        break;
    }
    return 0;
}

static const struct stream_operations uart_ops =
{
    .open   = usart_open,
    .on_eof = usart_eof,
    .ctrl   = usart_ctrl,
};

static const struct usart_cfg usart_cfg_def = SERIAL_CONFIG_DEFAULT;
int uart_dev_add(const char *name, const struct usart_cfg *cfg)
{
    int ret;
    struct uart_port *port = kmalloc(sizeof(struct uart_port));

    if (!port) return -ENOMEM;

    memset(port, 0, sizeof(struct uart_port));
    if (!cfg) cfg = &usart_cfg_def;
    memcpy(&port->cfg, cfg, sizeof(struct usart_cfg));

    port->dev.ops = &uart_ops;

    ret = stream_device_register(&port->dev, name, NULL);
    if (ret < 0) kfree(port);

    return ret;
}
void uart_dev_del(device_t *dev)
{
    struct stream_device *stream_dev = to_stream_dev(dev);
    struct uart_port *port = to_port(stream_dev);

    stream_device_unregister(stream_dev);
    kfree(port);
}

